File: LanguageService\AbstractLanguageService`2.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Structure;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.Venus;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Outlining;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
 
internal abstract partial class AbstractLanguageService<TPackage, TLanguageService> : AbstractLanguageService
    where TPackage : AbstractPackage<TPackage, TLanguageService>
    where TLanguageService : AbstractLanguageService<TPackage, TLanguageService>
{
    internal TPackage Package { get; }
 
    private readonly Lazy<VsLanguageDebugInfo> _languageDebugInfo;
 
    internal readonly Lazy<VisualStudioWorkspaceImpl> Workspace;
 
    protected abstract string ContentTypeName { get; }
    protected abstract string LanguageName { get; }
    protected abstract string RoslynLanguageName { get; }
    protected abstract Guid DebuggerLanguageId { get; }
 
    protected AbstractLanguageService(TPackage package)
    {
        Package = package;
 
        Debug.Assert(!this.Package.JoinableTaskFactory.Context.IsOnMainThread, "Language service should be instantiated on background thread");
 
        this.Workspace = this.Package.ComponentModel.DefaultExportProvider.GetExport<VisualStudioWorkspaceImpl>();
 
        this._languageDebugInfo = new Lazy<VsLanguageDebugInfo>(CreateLanguageDebugInfo);
    }
 
    private IThreadingContext ThreadingContext => this.Package.ComponentModel.GetService<IThreadingContext>();
 
    public override IServiceProvider SystemServiceProvider
        => Package;
 
    public void Setup(CancellationToken cancellationToken)
    {
        // Start off a background task to prime some components we'll need for editing.
        Task.Run(() =>
        {
            var formatter = this.Workspace.Value.Services.GetLanguageServices(RoslynLanguageName).GetService<ISyntaxFormattingService>();
            formatter?.GetDefaultFormattingRules();
        }, cancellationToken).Forget();
    }
 
    protected virtual void SetupNewTextView(IVsTextView textView)
    {
        Contract.ThrowIfNull(textView);
 
        var editorAdaptersFactoryService = this.Package.ComponentModel.GetService<IVsEditorAdaptersFactoryService>();
        var wpfTextView = editorAdaptersFactoryService.GetWpfTextView(textView);
        Contract.ThrowIfNull(wpfTextView, "Could not get IWpfTextView for IVsTextView");
 
        Debug.Assert(!wpfTextView.Properties.ContainsProperty(typeof(AbstractVsTextViewFilter)));
 
        // The lifetime of CommandFilter is married to the view
        wpfTextView.GetOrCreateAutoClosingProperty(v =>
            new StandaloneCommandFilter(
                v, Package.ComponentModel).AttachToVsTextView());
 
        var isMetadataAsSource = false;
        var collapseAllImplementations = false;
 
        var openDocument = wpfTextView.TextBuffer.AsTextContainer().GetRelatedDocuments().FirstOrDefault();
        if (openDocument?.Project.Solution.Workspace is MetadataAsSourceWorkspace masWorkspace)
        {
            isMetadataAsSource = true;
 
            // If the file is metadata as source, and the user has the preference set to collapse them, then
            // always collapse all metadata as source
            var globalOptions = this.Package.ComponentModel.GetService<IGlobalOptionService>();
            var options = BlockStructureOptionsStorage.GetBlockStructureOptions(globalOptions, openDocument.Project.Language, isMetadataAsSource: true);
            collapseAllImplementations = masWorkspace.FileService.ShouldCollapseOnOpen(openDocument.FilePath, options);
        }
 
        ConditionallyCollapseOutliningRegions(textView, wpfTextView, collapseAllImplementations);
 
        // If this is a metadata-to-source view, we want to consider the file read-only and prevent
        // it from being re-opened when VS is opened
        if (isMetadataAsSource && ErrorHandler.Succeeded(textView.GetBuffer(out var vsTextLines)))
        {
            Contract.ThrowIfNull(openDocument);
 
            ErrorHandler.ThrowOnFailure(vsTextLines.GetStateFlags(out var flags));
            flags |= (int)BUFFERSTATEFLAGS.BSF_USER_READONLY;
            ErrorHandler.ThrowOnFailure(vsTextLines.SetStateFlags(flags));
 
            var runningDocumentTable = (IVsRunningDocumentTable)SystemServiceProvider.GetService(typeof(SVsRunningDocumentTable));
            var runningDocumentTable4 = (IVsRunningDocumentTable4)runningDocumentTable;
 
            if (runningDocumentTable4.IsMonikerValid(openDocument.FilePath))
            {
                var cookie = runningDocumentTable4.GetDocumentCookie(openDocument.FilePath);
                runningDocumentTable.ModifyDocumentFlags(cookie, (uint)_VSRDTFLAGS.RDT_DontAddToMRU | (uint)_VSRDTFLAGS.RDT_CantSave | (uint)_VSRDTFLAGS.RDT_DontAutoOpen, fSet: 1);
            }
        }
    }
 
    private void ConditionallyCollapseOutliningRegions(IVsTextView textView, IWpfTextView wpfTextView, bool collapseAllImplementations)
    {
        var outliningManagerService = this.Package.ComponentModel.GetService<IOutliningManagerService>();
        var outliningManager = outliningManagerService.GetOutliningManager(wpfTextView);
        if (outliningManager == null)
            return;
 
        if (textView is IVsTextViewEx viewEx)
        {
            if (collapseAllImplementations)
            {
                // If this file is a metadata-from-source file, we want to force-collapse any implementations
                // to keep the display clean and condensed.
                outliningManager.CollapseAll(wpfTextView.TextBuffer.CurrentSnapshot.GetFullSpan(), c => c.Tag.IsImplementation);
            }
            else
            {
                // Otherwise, attempt to persist any outlining state we have computed. This
                // ensures that any new opened files that have any IsDefaultCollapsed spans
                // will both have them collapsed and remembered in the SUO file.
                // NOTE: Despite its name, this call will LOAD the state from the SUO file,
                //       or collapse to definitions if it can't do that.
                viewEx.PersistOutliningState();
            }
        }
    }
 
    private VsLanguageDebugInfo CreateLanguageDebugInfo()
    {
        var languageServices = this.Workspace.Value.Services.GetLanguageServices(RoslynLanguageName);
 
        return new VsLanguageDebugInfo(
            this.DebuggerLanguageId,
            (TLanguageService)this,
            languageServices,
            this.Package.ComponentModel.GetService<IUIThreadOperationExecutor>());
    }
 
    protected virtual IVsContainedLanguage CreateContainedLanguage(
        IVsTextBufferCoordinator bufferCoordinator, ProjectSystemProject project,
        IVsHierarchy hierarchy, uint itemid)
    {
        return new ContainedLanguage(
            bufferCoordinator,
            this.Package.ComponentModel,
            this.Workspace.Value,
            project.Id,
            project,
            this.LanguageServiceId);
    }
}